using System;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Text;
using System.Timers;
using System.Threading;
using System.IO;

namespace LANamp
{
	/// <summary>
	/// The LANamp client library
	/// </summary>
	public class LanAmpClient
	{
        public delegate void Transmission();
        public delegate void ServerDisconnectedEvent();
        public delegate void WatchedServerChangedEvent();
        public delegate void NewSongInfoEvent(string NewSongInfo);
        public delegate void NewNextUserEvent(string nextUser);
        public delegate void NewSkipVoteEvent(int skipVote);
        public delegate void NewSkipVoteDoneEvent(string whoSkipvoted);
        public delegate void PlayListUpdateEvent();

        public event ServerDisconnectedEvent ServerDisconnected;
		public event ClientList.UpdateEvent ClientsUpdated;
        public event ServerList.UpdateEvent ServersUpdated;
        public event WatchedServerChangedEvent WatchedServerChanged;
        public event NewSongInfoEvent NewSongInfo;
        public event NewNextUserEvent NewNextUser;
        public event NewSkipVoteEvent NewSkipVote;
        public event NewSkipVoteDoneEvent NewSkipVoteDone;
        public event PlayListUpdateEvent PlayListUpdate;
        public event Transmission Rx;
        public event Transmission Tx;
		
		public LanAmpClient(System.Windows.Forms.Control Owner, string AppVersion)
		{
            owner = Owner;
			localIP = new IPEndPoint(IPAddress.Any, 8642);
			clients = new ClientList(5);
            servers = new ServerList(5);
            indexList = new PlayList();
            playList = new PlayList();
            playList.Updated += new PlayList.UpdatedEvent(playList_Updated);
			clients.Updated += new ClientList.UpdateEvent(clients_Updated);
            servers.Updated += new ServerList.UpdateEvent(servers_Updated);
            servers.ServerGone += new ServerList.ServerGoneEvent(servers_ServerGone);
			receiver = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
			try 
            {
                receiver.Bind(localIP);
            }
            catch (System.Exception e) 
            {
                System.Console.Out.WriteLine("ERROR: Can't bind to local socket; original error follows:\n"+e.ToString());
            }
			sendIP = new IPEndPoint(IPAddress.Broadcast,8642);
            self = new Client(Client.ToIP(localIP), "<lobby>", System.Environment.UserName, System.Environment.MachineName, AppVersion,99);
			enc = Encoding.Unicode;
			sender = new UdpClient();
			aliveTimer = new System.Timers.Timer(3000);
			aliveTimer.Elapsed += new ElapsedEventHandler(TimeOut);
			aliveTimer.Start();
            if (receiver.IsBound)
            {
                udplistener = new Thread(new ThreadStart(ReceiveLoop));
                udplistener.Name = "UdpListener";
                udplistener.IsBackground = true;
                udplistener.Start();
            }
		}


		~LanAmpClient()
		{
			ClientsUpdated = null;
		}

		public void Kill()
		{
			if (udplistener != null) 
            {
                aliveTimer.Stop();
			    TellGone();
			    //listener.Abort();
            }
		}

        public Server WatchedServer
        {
            get { return watchedserver; }
            set
            {
                if (watchedserver != value)
                {
                    watchedserver = value;
                    ampclient_WatchedServerChanged();
                    this.clients_Updated((watchedserver != null) ? watchedserver.IPaddress : "<lobby>");
                }
            }
        }

        public Server ConnectedServer
        {
            get
            {
                return connectedserver;
            }
            set
            {
                if (value != null)
                {
                    // connect
                    if (TcpConnect(value.IPaddress))
                    {
                        indexList.Clear();
                        playList.Clear();
                        connectedserver = value;
                        self.Server = value.IPaddress;
                        self.SkipVote = false;
                        TellAlive();
                        tcplistener = new Thread(new ThreadStart(TcpReceiveTransmission));
                        tcplistener.Name = "TcpListener";
                        tcplistener.IsBackground = true;
                        tcplistener.Start();
                    }
                }
                else
                {
                    if (connectedserver != null)
                    // disconnect
                    {
                        if (tcpclient != null)
                        {
                            if (tcpclient.Client != null) if (tcpclient.Client.Connected) tcpclient.Client.Disconnect(false);
                            tcpclient.Close();
                            tcpclient = null;
                        }
                        TellGone();
                        indexList.Clear();
                        playList.Clear();
                        self.SkipVote = false;
                        connectedserver = null;
                        self.Server = "<lobby>";
                        TellAlive();
                        if (ServerDisconnected != null)
                        {
                            if (owner.InvokeRequired)
                                owner.BeginInvoke(new ServerDisconnectedEvent(ServerDisconnected));
                            else
                                ServerDisconnected();
                        }
                    }
                }
            }
        }

        private bool TcpConnect(string IPaddress)
        {
            if (tcpclient != null) if (tcpclient.Connected)
                {
                    tcpclient.Client.Close();
                    tcpclient.Close();
                }
            tcpclient = new TcpClient();
            try
            {
                tcpclient.Connect(IPaddress, 8642);
            }
            catch
            {
                return false;
            }
            if (tcpclient.Connected)
            {
                SendFullTCPMessage("LANamp-CONNECT");
            }
            return true;
        }

        public bool ServerConnected
        {
            get { return (connectedserver != null); }
        }

		public ClientList Clients
		{
			get
			{
				return clients;
			}
		}

        public ServerList Servers
        {
            get
            {
                return servers;
            }
        }

		private void TimeOut(object source, ElapsedEventArgs e) 
		{
			clients.TimeOut();
            servers.TimeOut();
			TellAlive();
		}

		private void TellAlive()
		{
			SendFullMessage("LANamp-ANNOUNCE");
		}

		private void TellGone()
		{
			SendMessage("LANamp-BYEBYE");
		}

        private void TellSkipVote()
        {
            SendMessage("LANamp-SKIPVOTE");
        }

        public bool SkipVote
        {
            get { return self.SkipVote;  }
            set
            {
                if (value)
                {
                    if (!self.SkipVote)
                    {
                        self.SkipVote = true;
                        TellSkipVote();
                    }
                }
            }
        }

        public void SendTCPMessage(string message, string[] messageData)
        {
            Byte[] rawData;
            String data;
            data = message + "\n" + String.Join("\0", messageData);
            rawData = enc.GetBytes(data);
            SendSignal();
            try
            {
                tcpclient.Client.Send(rawData);
            }
            catch
            {
                Console.WriteLine("TCP/IP socket down");
                this.ConnectedServer = null;
            }
        }

        public void SendFullTCPMessage(string message)
        {
            Byte[] rawData;
            String data;
            String[] parts = new String[5];
            parts[0] = message;
            parts[1] = self.Server;
            parts[2] = self.Nickname;
            parts[3] = self.Hostname;
            parts[4] = self.Version;
            data = String.Join("\0", parts);
            rawData = enc.GetBytes(data);
            SendSignal();
            try
            {
                tcpclient.Client.Send(rawData);
            }
            catch
            {
                Console.WriteLine("TCP/IP socket down");
                this.ConnectedServer = null;
            }
        }

        public void SendMessage(String message)
		{
			Byte[] rawData;
			String data;
			data = message + "\0";
			rawData = enc.GetBytes(data);
            SendSignal();
            try
            {
                sender.Send(rawData, rawData.Length, sendIP);
            }
            catch
            {
                Console.WriteLine("No network available to send broadcasts over");
            }
		}

        public void SendFullMessage(String message)
        {
            Byte[] rawData;
            String data;
            String[] parts = new String[5];
            parts[0] = message;
            parts[1] = self.Server;
            parts[2] = self.Nickname;
            parts[3] = self.Hostname;
            parts[4] = self.Version;
            data = String.Join("\0", parts);
            rawData = enc.GetBytes(data);
            SendSignal();
            try
            {
                sender.Send(rawData, rawData.Length, sendIP);
            }
            catch
            {
                Console.WriteLine("No network available to send broadcasts over");
            }
        }

        private void SendSignal()
        {
        if (Tx != null)
            {
                if (owner.InvokeRequired)
                    owner.BeginInvoke(new Transmission(Tx));
                else
                    Tx();
            }
        }

        private void ReceiveSignal()
        {
            if (Rx != null)
            {
                if (owner.InvokeRequired)
                    owner.BeginInvoke(new Transmission(Rx));
                else
                    Rx();
            }
        }

        private void TcpReceiveTransmission()
        {
            StreamReader reader = new StreamReader(tcpclient.GetStream());
            StreamWriter writer = new StreamWriter(tcpclient.GetStream());
            while ((tcplistener.IsAlive) && (tcpclient != null))
            {
                try
                {
                    if ((tcpclient != null) && (tcpclient.Client != null) && (tcpclient.Available > 0))
                    {
                        ReceiveSignal();
                        string command = reader.ReadLine();
                        switch (command)
                        {
                            case "LANamp-PLAYLISTRESET":
                                {
                                    indexList.Clear();
                                    break;
                                }
                            case "LANamp-PLAYLISTSORT":
                                {
                                    indexList.Sort();
                                    break;
                                }
                            case "LANamp-SKIPVOTED":
                                {
                                    string whoSkipvoted = reader.ReadLine();
                                    object[] args = new object[1];
                                    args[0] = whoSkipvoted.Clone();
                                    if (NewSkipVoteDone != null) owner.BeginInvoke(NewSkipVoteDone,args);
                                    break;
                                }
                            case "LANamp-PLAYLISTITEM":
                                {
                                    MusicInfo song = new MusicInfo(reader);
                                    indexList.Add(song);
                                    break;
                                }
                            case "LANamp-GETNEXT":
                                {
                                    MusicInfo song = playList.GetNext();
                                    if (song == null)
                                        song = new MusicInfo();
                                    writer.WriteLine("LANamp-SETNEXT");
                                    song.WriteToStream(writer);
                                    writer.Flush();
                                    if (song.DisplayName != "")
                                    {
                                        {
                                            indexList.Add(song);
                                            indexList.Sort();
                                        }
                                    }
                                    break;
                                }
                            case "LANamp-CLEARSKIPVOTE":
                                {
                                    self.SkipVote = false;
                                    break;
                                }
                        }
                        writer.Flush();
                    }
                    else
                    {
                        Thread.Sleep(10);
                    }
                }
                catch
                {
                }
            }
            reader = null;
            writer = null;
        }


        public void ReceiveLoop() 
		{
			try 
			{
				Byte[] rawData;
				String data;
				EndPoint remoteIP = new IPEndPoint(IPAddress.Any, 0); //dummy endpoint
				while ((udplistener.IsAlive) && (receiver.IsBound))
				{
                    if (receiver.Available > 0)
                    {
                        rawData = new Byte[254];
                        int length = receiver.ReceiveFrom(rawData, ref remoteIP);
                        ReceiveSignal();
                        data = enc.GetString(rawData).TrimEnd('\0');
                        string[] command = data.Split(new char[1] { '\0' });
                        switch (command[0])
                        {
                            case "LANamp-ANNOUNCE":
                                {
                                    if (command.Length < 2) break;
                                    string appversion = (command.Length > 4) ? command[4].Trim() : "(unknown)";
                                    string hostname = (command.Length > 3) ? command[3].Trim() : "";
                                    string nickname = (command.Length > 2) ? command[2].Trim() : "(anonymous)";
                                    string server = command[1].Trim();
                                    Console.Out.WriteLine("Alive: " + nickname + "@" + hostname + " (" + remoteIP.ToString() + ") v" + appversion + " / " + server);
                                    clients.Alive(remoteIP, server, nickname, hostname, appversion);
                                    break;
                                }
                            case "LANamp-SERVERONLINE":
                                {
                                    if (command.Length < 2) break;
                                    string winampversion = (command.Length > 4) ? command[4].Trim() : "(unknown)";
                                    string appversion = (command.Length > 3) ? command[3].Trim() : "(unknown)";
                                    string hostname = (command.Length > 2) ? command[2].Trim() : "";
                                    string name = command[1].Trim();
                                    Console.Out.WriteLine("ServerAlive: " + name + "@" + hostname + " (" + remoteIP.ToString() + ") v" + appversion + "-" + winampversion);
                                    servers.Alive(remoteIP, name, hostname, appversion, winampversion);
                                    break;
                                }
                            case "LANamp-BYEBYE":
                                {
                                    Client client = clients.GetClient(remoteIP);
                                    if (client != null)
                                    {
                                        Console.Out.WriteLine("Gone: " + client.Nickname + "@" + client.Host + " / " + client.Server);
                                    }
                                    clients.Gone(remoteIP);
                                    break;
                                }
                            case "LANamp-SERVEROFFLINE":
                                {
                                    Server server = servers.GetServer(remoteIP);
                                    if (server != null) Console.Out.WriteLine("ServerOff: " + server.Name + "@" + server.Host);
                                    servers.Gone(remoteIP);
                                    break;
                                }
                            case "LANamp-SERVERUPDATE":
                                {
                                    if (ConnectedServer != null)
                                    {
                                        if (Client.ToIP(remoteIP) == ConnectedServer.IPaddress)
                                        {
                                            if (command.Length < 3) break;
                                            int skipvote = (command.Length > 6) ? int.Parse(command[6].Trim()) : 0;
                                            string nextuser = (command.Length > 5) ? command[5].Trim() : "(unknown)";
                                            string tracklength = (command.Length > 4) ? command[4].Trim() : "(unknown)";
                                            string trackposition = (command.Length > 3) ? command[3].Trim() : "(unknown)";
                                            string trackname = (command.Length > 2) ? command[2].Trim() : "";
                                            string name = command[1].Trim();
                                            newSongInfo(trackname.Replace(" - ", "\n") + "\n" + "(" + trackposition + " / " + tracklength + ")");
                                            newSkipVote(skipvote);
                                            newNextUser(nextuser);
                                        }
                                    }
                                    break;
                                }
                        }
                    }
                    else
                    {
                        Thread.Sleep(10);
                    }
                }
            }
			catch (ThreadAbortException) 
			{
			}
		}
		public String Nickname
		{
			get
			{
				return self.Nickname;
			}
			set
			{
				self.Nickname = value;
                TellAlive();
			}
		}

        public PlayList Playlist
        {
            get
            {
                return playList;
            }
        }

        public PlayList IndexList
        {
            get
            {
                return indexList;
            }
        }
        private void newSongInfo(string songinfo)
        {
            if (NewSongInfo != null)
            {
                if (owner.InvokeRequired)
                {
                    object[] myArgs = new object[1];
                    myArgs[0] = songinfo.Clone();
                    owner.BeginInvoke(new NewSongInfoEvent(NewSongInfo), myArgs);
                }
                else
                {
                    NewSongInfo(songinfo);
                }
            }
        }

        private void newNextUser(string nextuser)
        {
            if (NewNextUser != null)
            {
                if (owner.InvokeRequired)
                {
                    object[] myArgs = new object[1];
                    myArgs[0] = nextuser.Clone();
                    owner.BeginInvoke(new NewNextUserEvent(NewNextUser), myArgs);
                }
                else
                {
                    NewNextUser(nextuser);
                }
            }
        }

        private void newSkipVote(int skipVote)
        {
            if (NewSkipVote != null)
            {
                if (owner.InvokeRequired)
                {
                    object[] myArgs = new object[1];
                    myArgs[0] = skipVote;
                    owner.BeginInvoke(new NewSkipVoteEvent(NewSkipVote), myArgs);
                }
                else
                {
                    NewSkipVote(skipVote);
                }
            }
        }

        public void InsertPlayListItem(MusicInfo song)
        {
            playList.Insert(song);
            indexList.Remove(song);
        }

        public void AddPlayListItem(MusicInfo song)
        {
            playList.Add(song);
            indexList.Remove(song);
        }

        public void RemovePlayListItem(MusicInfo song)
        {
            indexList.Add(song);
            playList.Remove(song);
        }

        private Client self;
        private System.Windows.Forms.Control owner;
		private IPEndPoint localIP;
		private IPEndPoint sendIP;
		private ClientList clients;
        private ServerList servers;
		private Encoding enc;
		private Socket receiver;
		private UdpClient sender;
        private TcpClient tcpclient;
		private System.Timers.Timer aliveTimer;
		private Thread udplistener;
        private Thread tcplistener;
        private Server watchedserver;
        private Server connectedserver;
        private PlayList indexList;
        private PlayList playList;

        void playList_Updated()
        {
            if (PlayListUpdate != null)
            {
                if (owner.InvokeRequired)
                    owner.BeginInvoke(new PlayListUpdateEvent(PlayListUpdate));
                else
                    PlayListUpdate();
            }
        }

        void servers_ServerGone(Server server)
        {
            if (server == connectedserver)
            {
                ConnectedServer = null;
            }
        }

        void servers_Updated()
        {
            bool minefound = false;
            foreach (Server server in Servers.Items)
            {
                if (server == watchedserver) minefound = true;
            }
            if ((!minefound) && (watchedserver != null)) WatchedServer = null;
            if (ServersUpdated != null)
            {
                if (owner.InvokeRequired)
                    owner.BeginInvoke(new ServerList.UpdateEvent(servers_Updated));
                else
                    ServersUpdated();
            }
        }

        private void clients_Updated(string ServerIP)
		{
            if (ClientsUpdated != null)
            {
                if (owner.InvokeRequired)
                {
                    object[] myArgs = new object[1];
                    myArgs[0] = ServerIP.Clone();
                    owner.BeginInvoke(new ClientList.UpdateEvent(clients_Updated), myArgs);
                }
                else
                {
                    ClientsUpdated(ServerIP);
                }
            }
		}

        private void ampclient_WatchedServerChanged()
        {
            if (WatchedServerChanged != null)
            {
                if (owner.InvokeRequired)
                {
                    owner.BeginInvoke(new WatchedServerChangedEvent(ampclient_WatchedServerChanged));
                }
                else
                {
                    WatchedServerChanged();
                }
            }
        }
	}

}
